#include "utility_api.h"

// add necessary includes here
#include "utils/can/can_vars.h"
#include "utils/algorithm/i_algorithm_bll.h"

#define DF_SEC_INTERVAL                 1000
#define DF_INDOOR_TEMPERATURE           25
#define DF_SUCTION_OFFSET               105
#define DF_EXT_A_HOLE_AMOUNT            1
#define DF_EXT_B_HOLE_AMOUNT            11
#define DF_EXT_HOLE_AMOUNT              DF_EXT_A_HOLE_AMOUNT + DF_EXT_B_HOLE_AMOUNT
#define DF_PCR_HOLE_AMOUNT              8

#define DF_PCR_MIN_DURATION             7.5
#define DF_PCR_CHANNEL_SAMPLING         4
#define DF_MIN_TEMP_VALUE               1.0


const static int gs_nExtHoleSpacing10[DF_EXT_HOLE_AMOUNT - 2] = {
    MotorVars_T1C::nExtHoleSpacing2_B_9a,
    MotorVars_T1C::nExtHoleSpacing2_B_89,
    MotorVars_T1C::nExtHoleSpacing2_B_78,
    MotorVars_T1C::nExtHoleSpacing2_B_67,
    MotorVars_T1C::nExtHoleSpacing2_B_56,
    MotorVars_T1C::nExtHoleSpacing2_B_45,
    MotorVars_T1C::nExtHoleSpacing2_B_34,
    MotorVars_T1C::nExtHoleSpacing2_B_23,
    MotorVars_T1C::nExtHoleSpacing2_B_12,
    MotorVars_T1C::nExtHoleSpacing2_A2B1
};
const static int gs_nExtHoleSpacing11[DF_EXT_HOLE_AMOUNT - 1] = {
    MotorVars_T1C::nExtHoleSpacing_B_ab,
    MotorVars_T1C::nExtHoleSpacing_B_9a,
    MotorVars_T1C::nExtHoleSpacing_B_89,
    MotorVars_T1C::nExtHoleSpacing_B_78,
    MotorVars_T1C::nExtHoleSpacing_B_67,
    MotorVars_T1C::nExtHoleSpacing_B_56,
    MotorVars_T1C::nExtHoleSpacing_B_45,
    MotorVars_T1C::nExtHoleSpacing_B_34,
    MotorVars_T1C::nExtHoleSpacing_B_23,
    MotorVars_T1C::nExtHoleSpacing_B_12,
    MotorVars_T1C::nExtHoleSpacing_A2B1
};
const static int gs_nPcrHoleSpacing[DF_PCR_HOLE_AMOUNT - 1] = {
    MotorVars_T1C::nPcrHoleSpacing_C,
    MotorVars_T1C::nPcrHoleSpacing_C,
    MotorVars_T1C::nPcrHoleSpacing_C,
    MotorVars_T1C::nPcrHoleSpacing_C,
    MotorVars_T1C::nPcrHoleSpacing_C,
    MotorVars_T1C::nPcrHoleSpacing_C,
    MotorVars_T1C::nPcrHoleSpacing_C
};

UtilityAPI* UtilityAPI::getInstance(QObject *pParent)
{
    static UtilityAPI *pNew = nullptr;
    if(!pNew) {
        qDebug() << Q_FUNC_INFO;

        // Create a new object if it doesn't exist
        pNew = new UtilityAPI(pParent);
        if(nullptr == pNew) return nullptr;
    }

    // Set the parent object
    if(pParent) pNew->setParent(pParent);

    return pNew;
}

int UtilityAPI::splitProcedureAction(QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, ST_MOTOR_STATE stMotorState)
{
    qDebug() << Q_FUNC_INFO << stMotorState.nTrayOffset << stMotorState.nMagenticRodOffset << stMotorState.nMagenticSleeveOffset << stMotorState.nPipetteOffset << stMotorState.nSuctionSpitOffset;
    queFlowProcAction.clear();
    m_fTimeConsuming = 0.0;

    int nRetVal = 0;

    // 1 Discard tipspipe
    // 2 Discard drivepipe
    // 3 Reset all motors

    int nCmdIdx = 0;
    int nStepIdx = 0;
    QList<QString> lstAction;

    // Move the pipette motor to its initial position
    if(0 < stMotorState.nPipetteOffset) {
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, MotorVars_T1C::nResetTipsOffsetY2);
    }

    // Move the magentic rod motor to its initial position
    if(0 < stMotorState.nMagenticRodOffset) {
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SKUP, stMotorState.nMagenticRodOffset);
    }

    // Move the magentic sleeve motor to its initial position
    if(0 != stMotorState.nMagenticSleeveOffset) {
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SLUP, abs(stMotorState.nMagenticSleeveOffset));
    }

    // Discard tipspipe
    if(stMotorState.bTipspipeGraped) {
        _switchTipspipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, false);
    }

    // Discard drivepipe
    if(stMotorState.bDrivepipeGraped) {
        _switchDrivepipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, false);
    }

    // Move the tray motor to its initial position
    if(0 < stMotorState.nTrayOffset) {
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_LOAD, stMotorState.nTrayOffset);
    }

    m_fTimeConsuming = stMotorState.fTimeConsuming;
    nRetVal = queFlowProcAction.size();

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

int UtilityAPI::splitProcedureAction(QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, const QList<ST_STEP_INFO> &lstModelData, int nKitType)
{
    qDebug() << Q_FUNC_INFO << lstModelData.size() << nKitType;
    queFlowProcAction.clear();
    m_fTimeConsuming = 0.0;

    int nRetVal = 0;

    if(0 >= lstModelData.size()) {
        nRetVal = -1;
    }

    // 1 Reset all motors
    // 2 Grap drivepipe
    // 3 Loop lookup the list of steps
    // 4 Discard drivepipe

    int nCmdIdx = 0;
    int nStepIdx = 0;
    bool bDrivepipeGraped = false;
    bool bTipspipeGraped = false;
    int nVolumeValue = -1;
    int nDuration = 0;
    int nCycles = 0;
    int nMotorSpeed = 0;
    QList<QString> lstAction;
    ST_MOTION_INFO stMotionInfo;

    ST_MOTOR_STATE stMotorState;
    stMotorState.nTrayOffset = 0;
    stMotorState.nMagenticRodOffset = 0;
    stMotorState.nMagenticSleeveOffset = 0;
    stMotorState.nPipetteOffset = 0;
    stMotorState.nSuctionSpitOffset = MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY;

    // ***************** Reset all motors *****************
    _resetMotors(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, true);

    if(0 < lstModelData.size()) {
        // ***************** Loop lookup the list of steps *****************
        for(int i=0, j = 0; i<lstModelData.size(); i++, j++) {
            nStepIdx = j;

            // Start heating (>25°C cover)
            if(DF_INDOOR_TEMPERATURE < lstModelData.at(i).fHeatingValue) {
                _switchHeating(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, lstModelData.at(i).nHoleNumber, nKitType, true, lstModelData.at(i).fHeatingValue);
            }

            // Get the step name
            QString sStepName = lstModelData.at(i).sStepName.trimmed();

            // ***************** Grap drivepipe *****************
            if(!bDrivepipeGraped) {
                bDrivepipeGraped = true;
                _switchDrivepipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, true);
            }

            if(DF_NAME_MAG_SLEEVE == sStepName || DF_NAME_MAG_SLEEVE_FAST == sStepName || DF_NAME_MAG_SLEEVE_SLOW == sStepName) {
                // Obtain adjacent action info
                _getAdjacentActionInfo(stMotionInfo, lstModelData, i);

                // ***************** Still *****************
                if(0 < lstModelData.at(i).nSilentDuration) {
                    nDuration = lstModelData.at(i).nSilentDuration * DF_SEC_INTERVAL;

                    // Silent for a moment
                    _doSilent(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nDuration, nKitType);
                }

                // ***************** Mixing *****************
                if(0 < lstModelData.at(i).nMixingDuration) {
                    nDuration = lstModelData.at(i).nMixingDuration * DF_SEC_INTERVAL;
                    nVolumeValue = lstModelData.at(i).nVolumeValue;

                    if(DF_NAME_MAG_SLEEVE_FAST == sStepName) nMotorSpeed = MotorVars_T1C::nMotorSpeedHigh;
                    else if(DF_NAME_MAG_SLEEVE_SLOW == sStepName) nMotorSpeed = MotorVars_T1C::nMotorSpeedLow;
                    else nMotorSpeed = MotorVars_T1C::nMotorSpeedMedium;

                    // Move the magnetic sleelve & mixing
                    _doSleelveMixing(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nDuration, nMotorSpeed, nKitType, nVolumeValue);
                }

                // ***************** Filtering Magnet *****************
                if(0 < lstModelData.at(i).nMagnetDuration) {
                    nDuration = lstModelData.at(i).nMagnetDuration * DF_SEC_INTERVAL;
                    nVolumeValue = lstModelData.at(i).nVolumeValue;

                    // Move the magnetic rod & filtering the magnetic bead
                    _doMagnetFiltering(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nDuration, nKitType, nVolumeValue);

                }
            }
            else if(DF_NAME_PIERCE == sStepName) {
                // ***************** Pierce *****************

                // Discard tipspipe
                if(bTipspipeGraped) {
                    bTipspipeGraped = false;
                    _switchTipspipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, false);
                }

                // Obtain adjacent action info
                _getAdjacentActionInfo(stMotionInfo, lstModelData, i);

                // Move the iron tip & piercing
                _doPiercing(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nKitType);
            }
            else if(DF_NAME_LOAD_TIPS == sStepName) {
                // Grap tipspipe
                if(!bTipspipeGraped) {
                    bTipspipeGraped = true;
                    _switchTipspipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, true);
                }
            }
            else if(DF_NAME_UNLOAD_TIPS  == sStepName) {
                // Discard tipspipe
                if(bTipspipeGraped) {
                    bTipspipeGraped = false;
                    _switchTipspipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, false);
                }
            }
            else if(DF_NAME_TIP_SUCTTION == sStepName) {
                // Grap tipspipe
                if(!bTipspipeGraped) {
                    bTipspipeGraped = true;
                    _switchTipspipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, true);
                }

                nVolumeValue = lstModelData.at(i).nVolumeValue;

                // Obtain adjacent action info
                _getAdjacentActionInfo(stMotionInfo, lstModelData, i);

                // Move the tippipe & suction
                _doTipSuctionSpit(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nVolumeValue, true, nKitType);
            }
            else if(DF_NAME_TIP_SPIT == sStepName) {
                // Grap tipspipe
                if(!bTipspipeGraped) {
                    bTipspipeGraped = true;
                    _switchTipspipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, true);
                }

                nVolumeValue = lstModelData.at(i).nVolumeValue;

                // Obtain adjacent action info
                _getAdjacentActionInfo(stMotionInfo, lstModelData, i);

                // Move the tippipe & spit
                _doTipSuctionSpit(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nVolumeValue, false, nKitType);
            }
            else if(DF_NAME_TIP_MIXING == sStepName) {
                // Grap tipspipe
                if(!bTipspipeGraped) {
                    bTipspipeGraped = true;
                    _switchTipspipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, true);
                }

                nVolumeValue = lstModelData.at(i).nVolumeValue;
                nCycles = lstModelData.at(i).nCyclePeriod;

                // Obtain adjacent action info
                _getAdjacentActionInfo(stMotionInfo, lstModelData, i);

                // Move the tippipe & mixing
                _doTipMixing(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nVolumeValue, nCycles, nKitType);
            }
            else if(DF_NAME_TIP_HEATING == sStepName) {
                // ***************** Still *****************
                if(0 < lstModelData.at(i).nSilentDuration) {
                    nDuration = lstModelData.at(i).nSilentDuration * DF_SEC_INTERVAL;

                    // Silent for a moment
                    _doSilent(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nDuration, nKitType);
                }
            }

            // Stop heating (>25°C cover)
            if(DF_INDOOR_TEMPERATURE < lstModelData.at(i).fHeatingValue) {
                _switchHeating(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, lstModelData.at(i).nHoleNumber, nKitType, false);
            }
        }

        // ***************** Stop heating (all channels) *****************
        _switchHeating(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, 0, nKitType, false);
    }

    // ***************** Discard tipspipe *****************
    if(bTipspipeGraped) {
        bTipspipeGraped = false;
        _switchTipspipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, false);
    }

    // ***************** Discard drivepipe *****************
    if(bDrivepipeGraped) {
        bDrivepipeGraped = false;
        _switchDrivepipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, false);
    }

    // ***************** Reset all motors *****************
    _resetMotors(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, false);

    m_fTimeConsuming = stMotorState.fTimeConsuming;
    nRetVal = queFlowProcAction.size();

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

int UtilityAPI::splitProcedureAction(QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, const QList<STC_PROCESS_TEST_INFO> &lstModelData)
{
    qDebug() << Q_FUNC_INFO << lstModelData.size();
    queFlowProcAction.clear();

    int nRetVal = 0;

    int nIdx = 0;
    if(0 < lstModelData.size()) {
        for(auto item : lstModelData) {
            // Transform list into queue
            ST_FLOW_PROC_ACTION stFlowProcAction;

            stFlowProcAction.nIdx = nIdx;
            stFlowProcAction.nStepIdx = nIdx;
            stFlowProcAction.nCmdID = item.nCommandId;
            if(!item.sParam.isEmpty()) stFlowProcAction.fParam = item.sParam.toFloat();
            if(!item.sParam2.isEmpty()) stFlowProcAction.fParam2 = item.sParam2.toFloat();
            if(!item.sParam3.isEmpty()) stFlowProcAction.fParam3 = item.sParam3.toFloat();

            queFlowProcAction.enqueue(stFlowProcAction);

            nIdx++;
        }
    }
    else {
        nRetVal = -1;
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

int UtilityAPI::splitProcedureAction(QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, const QString &sProcedureContent)
{
    qDebug() << Q_FUNC_INFO << sProcedureContent;
    queFlowProcAction.clear();

    int nRetVal = 0;

    QStringList lstProcedureContent = sProcedureContent.split(DEF_COMMAND_SEPARATOR);
    if(0 < lstProcedureContent.size()) {
        int nIdx = 0;

        foreach (QString sRowItems, lstProcedureContent) {
            ST_FLOW_PROC_ACTION stFlowProcAction;
            stFlowProcAction.nIdx = nIdx++;
            stFlowProcAction.nStepIdx = 0;

            // Fetch data into an structure
            QStringList lstItems = sRowItems.split(DEF_PARAM_SEPARATOR);
            for(int i=0; i<lstItems.size(); i++) {
                switch (i) {
                case 0: { stFlowProcAction.nCmdID = lstItems.at(i).toInt(); break; }
                case 1: { stFlowProcAction.fParam = lstItems.at(i).toInt(); break; }
                case 2: { stFlowProcAction.fParam2 = lstItems.at(i).toInt(); break; }
                case 3: { stFlowProcAction.fParam3 = lstItems.at(i).toInt(); break; }
                default: break;
                }
            }

            if(0 < stFlowProcAction.nCmdID) {
                queFlowProcAction << stFlowProcAction;
            }
        }

        nRetVal = queFlowProcAction.size();
    }
    else {
        nRetVal = -1;
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

int UtilityAPI::splitProcedureActionForCancelling(QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction)
{
    qDebug() << Q_FUNC_INFO;
    queFlowProcAction.clear();

    int nRetVal = 0;

    QString sCmdText = "";
    sCmdText += QString("%1,%2;").arg(E_TP_SLUP).arg(MotorVars_T1C::nMixingSleeveOffsetY);
    sCmdText += QString("%1;").arg(E_PA_Z_RESET);
    sCmdText += QString("%1;").arg(E_TP_SRST);

    splitProcedureAction(queFlowProcAction, sCmdText);

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

int UtilityAPI::getExtractStepInfo(QList<ST_STEP_INFO> &lstModelData, const QString &sProcedureContent)
{
    qDebug() << Q_FUNC_INFO << sProcedureContent;
    lstModelData.clear();

    int nRetVal = 0;

    QStringList lstProcedureContent = sProcedureContent.split(DEF_COMMAND_SEPARATOR);
    if(0 < lstProcedureContent.size()) {
        int nStepIdx = 0;

        // Fetch data into an structure
        foreach (QString sRowItems, lstProcedureContent) {
            QStringList lstItems = sRowItems.split(DEF_PARAM_SEPARATOR);
            if(EM_TCS_SIZE == lstItems.size()) {
                ST_STEP_INFO stDataItem;
                stDataItem.sStepName = lstItems.at(0);
                stDataItem.nStepIdx = nStepIdx++;
                stDataItem.sHoleType = lstItems.at(1);
                stDataItem.nHoleNumber = lstItems.at(2).toInt();
                stDataItem.nSilentDuration = lstItems.at(3).toInt();
                stDataItem.nMixingDuration = lstItems.at(4).toInt();
                stDataItem.nMagnetDuration = lstItems.at(5).toInt();
                stDataItem.nCyclePeriod = lstItems.at(6).toInt();
                stDataItem.nVolumeValue = lstItems.at(7).toInt();
                stDataItem.fHeatingValue = lstItems.at(8).toFloat();

                lstModelData << stDataItem;
            }
        }

        nRetVal = lstModelData.size();
    }
    else {
        nRetVal = -1;
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

int UtilityAPI::getPcrParamsInfo(QList<ST_PCR_PARAMS> &lstPcrParams, const ST_PCR_PROCEDURE &stPcrProc)
{
    qDebug() << Q_FUNC_INFO << stPcrProc.lstStage.size() << stPcrProc.nTimeConsume;
    lstPcrParams.clear();

    int nRetVal = 0;

    for(int i=0, nStageIdx = 0; i<stPcrProc.lstStage.size(); i++, nStageIdx++) {
        ST_PCR_PARAMS stPcrParams;
        bool bCyclesInStage = false;
        int nStageType = stPcrProc.lstStage.at(i).nType;
        int nCycles = stPcrProc.lstStage.at(i).nCycles;
        int nEntryCnt = stPcrProc.lstStage.at(i).lstEntry.size();

        if(0 <= nStageType && DEF_MELTING_MODE > nStageType) {
            for(int j=0; j<nEntryCnt; j++) {
                float fTemperature = stPcrProc.lstStage.at(i).lstEntry.at(j).fTemperature * 10;
                float fDuration = stPcrProc.lstStage.at(i).lstEntry.at(j).fDuration;
                bool bDetect = stPcrProc.lstStage.at(i).lstEntry.at(j).bDetect;

                if((DEF_MELTING_MODE - 1) == nStageType) {
                    // Melting temperature rise
                    float fFinalTemp = stPcrProc.lstStage.at(i).lstEntry.at(j).fFinalTemp * 10;
                    float fTempRate = stPcrProc.lstStage.at(i).lstEntry.at(j).fTempRate * 10;

                    if(DF_PCR_MIN_DURATION > fDuration) fDuration = DF_PCR_MIN_DURATION;

                    stPcrParams.stTempParams.nTemp1 = (int)fTemperature;
                    stPcrParams.stFluoDurations.nTime1 = (int)fFinalTemp;
                    stPcrParams.stFluoDurations.nTime2 = (int)fTempRate;
                    stPcrParams.stFluoDurations.nTime3 = (int)fDuration;
                    stPcrParams.stFluoDurations.nEnable1 = DF_PCR_CHANNEL_SAMPLING;
                }
                else {
                    // Regular temperature rise
                    switch (j) {
                    case 0:
                        stPcrParams.stTempParams.nTemp1 = (int)fTemperature;
                        stPcrParams.stFluoDurations.nTime1 = (int)fDuration;
                        stPcrParams.stFluoDurations.nEnable1 = (int)bDetect;
                        break;
                    case 1:
                        stPcrParams.stTempParams.nTemp2 = (int)fTemperature;
                        stPcrParams.stFluoDurations.nTime2 = (int)fDuration;
                        stPcrParams.stFluoDurations.nEnable2 = (int)bDetect;
                        break;
                    case 2:
                        stPcrParams.stTempParams.nTemp3 = (int)fTemperature;
                        stPcrParams.stFluoDurations.nTime3 = (int)fDuration;
                        stPcrParams.stFluoDurations.nEnable3 = (int)bDetect;
                        break;
                    case 3:
                        stPcrParams.stTempParams.nTemp4 = (int)fTemperature;
                        stPcrParams.stFluoDurations.nTime4 = (int)fDuration;
                        stPcrParams.stFluoDurations.nEnable4 = (int)bDetect;
                        break;
                    case 4:
                        stPcrParams.stTempParams.nTemp5 = (int)fTemperature;
                        stPcrParams.stFluoDurations.nTime5 = (int)fDuration;
                        stPcrParams.stFluoDurations.nEnable5 = (int)bDetect;
                        break;
                    default:
                        stPcrParams.stTempParams.nTemp1 = (int)fTemperature;
                        stPcrParams.stFluoDurations.nTime1 = (int)fDuration;
                        stPcrParams.stFluoDurations.nEnable1 = (int)bDetect;
                        break;
                    }

                    if(bDetect) {
                        bCyclesInStage = true;
                    }
                }

                stPcrParams.stTempParams.nStageIdx = nStageIdx;
            }

            stPcrParams.stTempParams.nCycles = nCycles;
            stPcrParams.stTempParams.nMode = nStageType + 1;
            stPcrParams.stTempParams.nStageCnt = stPcrProc.lstStage.size();

            if(bCyclesInStage) {
                stPcrParams.nPcrCycles = nCycles;
            }
        }

        lstPcrParams.push_back(stPcrParams);

        nRetVal += stPcrParams.nPcrCycles;
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

int UtilityAPI::struct2Json(const ST_PCR_PROCEDURE &stPcrProc, QByteArray &baJsonContext)
{
    baJsonContext.clear();
    int nRetVal = 0;

    QJsonDocument jdStage;
    QJsonObject joStage;
    QJsonArray jaStage;

    for(int i=0; i<stPcrProc.lstStage.size(); i++) {
        int nType = stPcrProc.lstStage.at(i).nType;
        int nCycles = stPcrProc.lstStage.at(i).nCycles;

        QJsonObject joStage;
        joStage.insert("type", QString::number(nType));
        joStage.insert("cycles", QString::number(nCycles));

        QJsonArray jaEntry;
        for(int j=0; j<stPcrProc.lstStage.at(i).lstEntry.size(); j++) {
            QJsonObject joEntry;
            joEntry.insert("temperature", QString::number(stPcrProc.lstStage.at(i).lstEntry.at(j).fTemperature));
            joEntry.insert("duration", QString::number(stPcrProc.lstStage.at(i).lstEntry.at(j).fDuration));
            joEntry.insert("detect", QString::number(stPcrProc.lstStage.at(i).lstEntry.at(j).bDetect));

            // Melting temperature rise
            if((DEF_MELTING_MODE - 1) == nType) {
                joEntry.insert("final_temp", QString::number(stPcrProc.lstStage.at(i).lstEntry.at(j).fFinalTemp));
                joEntry.insert("temp_rate", QString::number(stPcrProc.lstStage.at(i).lstEntry.at(j).fTempRate));
            }

            jaEntry.insert(j, joEntry);
        }

        joStage.insert("entry", jaEntry);
        jaStage.insert(i, joStage);
    }

    joStage.insert("stage", jaStage);
//    joStage.insert("time_consuming", QString::number(stPcrProc.nTimeConsume));
    jdStage.setObject(joStage);
    baJsonContext = jdStage.toJson(QJsonDocument::Compact);


#ifdef QT_NO_DEBUG
    qDebug() << Q_FUNC_INFO << nRetVal << stPcrProc.lstStage.size() << baJsonContext.size();
#else
    std::cout << Q_FUNC_INFO << nRetVal << stPcrProc.lstStage.size() << QString(baJsonContext).toStdString() << std::endl;
#endif

    return nRetVal;
}

int UtilityAPI::json2Struct(const QByteArray &baJsonContext, ST_PCR_PROCEDURE &stPcrProc)
{
    stPcrProc.clear();

    int nRetVal = 0;

    QJsonParseError jpError;
    QJsonDocument jdStage = QJsonDocument::fromJson(baJsonContext, &jpError);
    if(QJsonParseError::NoError == jpError.error) {
        QList<ST_PCR_PROC_STAGE> lstStage;

        QJsonObject joStage = jdStage.object();
        QString sTimeConsuming = joStage.value("time_consuming").toString();
        QJsonArray jaStage = joStage.value("stage").toArray();
        for(int i=0; i<jaStage.size(); i++) {
            QJsonObject joEntryList = jaStage.at(i).toObject();

            QString sType = joEntryList.value("type").toString();
            qDebug() << Q_FUNC_INFO << i << sType;

            QString sCycles = joEntryList.value("cycles").toString();
            qDebug() << Q_FUNC_INFO << i << sCycles;

            // serialize data
            ST_PCR_PROC_STAGE stStage;
            QList<ST_PCR_PROC_ENTRY> lstEntry;

            stStage.nType = sType.toInt();
            stStage.nCycles = sCycles.toInt();

            QJsonArray jaEntry = joEntryList.value("entry").toArray();
            for(int j=0; j<jaEntry.size(); j++) {
                ST_PCR_PROC_ENTRY stEntry;

                QJsonObject joEntry = jaEntry.at(j).toObject();
                QString sTemperature = joEntry.value("temperature").toString();
                QString sFinalTemp = joEntry.value("final_temp").toString();
                QString sTempRate = joEntry.value("temp_rate").toString();
                QString sDuration = joEntry.value("duration").toString();
                QString sDetect = joEntry.value("detect").toString();

                qDebug() << Q_FUNC_INFO << i << j << sDetect << sDuration << sTemperature;

                stEntry.fTemperature = sTemperature.toFloat();
                stEntry.fFinalTemp = sFinalTemp.toFloat();
                stEntry.fTempRate = sTempRate.toFloat();
                stEntry.fDuration = sDuration.toFloat();
                stEntry.bDetect = (bool)sDetect.toInt();

                stStage.lstEntry.push_back(stEntry);
            }

            lstStage.push_back(stStage);
        }

        stPcrProc.lstStage = lstStage;
//        stPcrProc.nTimeConsume = sTimeConsuming.toInt();

        if(0 == lstStage.size()) {
            nRetVal = -2;
        }
    }
    else {
        nRetVal = -1;
        qDebug() << Q_FUNC_INFO << jpError.errorString();
    }

#ifdef QT_NO_DEBUG
    qDebug() << Q_FUNC_INFO << nRetVal << baJsonContext.size();
#else
    std::cout << Q_FUNC_INFO << nRetVal << QString(baJsonContext).toStdString() << std::endl;
#endif

    return nRetVal;
}

int UtilityAPI::map2Json(QByteArray &baJsonData, const QMap<unsigned int, QVector<QPointF>> &mapValue, bool bTempChart)
{
    baJsonData.clear();

    int nRetVal = 0;

    if(bTempChart) {
        // construct a temperature json node
        if(0 < mapValue.size()) {
            QJsonDocument jdRootTemp;
            QJsonObject joRootTemp;

            int nTempSize = 0;
            QJsonObject joNodeTemp;
            QJsonArray jaRangeTemp;

            // Analyze all temperature data from the chart
            QVector<QPointF> vecTempPoints = mapValue[0];
            if(0 < vecTempPoints.size()) {
                QVector<QPointF>::Iterator itor;

                for(itor = vecTempPoints.begin(); itor != vecTempPoints.end(); itor++) {
                    int nCycleIdx = jaRangeTemp.size();
                    QString sY = QString("%1").arg((*itor).y());

                    jaRangeTemp.insert(nCycleIdx, sY);
                    nTempSize++;
                }
            }

            joNodeTemp.insert(DEF_JSON_TEMP_SIZE, QString::number(nTempSize));
            joNodeTemp.insert(DEF_JSON_TEMP_VALUE, jaRangeTemp);

            joRootTemp.insert(DEF_JSON_TEMP_DATA, joNodeTemp);

            // Data serialized
            jdRootTemp.setObject(joRootTemp);
            baJsonData = jdRootTemp.toJson(QJsonDocument::Compact);

#ifdef QT_NO_DEBUG
            qDebug() << Q_FUNC_INFO << QString(baJsonData);
#else
            std::cout << Q_FUNC_INFO << QString(baJsonData).toStdString() << std::endl;
#endif
        }
    }
    else {
        // construct a fluorescence json node
        if(DEF_T1C_HOLE_COUNT * DEF_T1C_LED_COUNT== mapValue.size()) {
            QJsonDocument jdRootFluo;
            QJsonObject joRootFluo;

            for (int i=0; i<DEF_T1C_HOLE_COUNT; i++) {
                QJsonObject joNodeHole;

                for(int j=0; j<DEF_T1C_LED_COUNT; j++) {
                    QJsonObject joNodeChannel;
                    QJsonArray jaRangeFluo;

                    int nOffset = i * DEF_T1C_LED_COUNT + j;
                    QVector<QPointF> vecFluoPoints = mapValue[nOffset];

                    QVector<QPointF>::Iterator itor;
                    for(itor = vecFluoPoints.begin(); itor != vecFluoPoints.end(); itor++) {
                        int nX = (*itor).x();
                        QString sY = QString::number((*itor).y(), 'f', 2);
                        jaRangeFluo.insert(nX, sY);
                    }

                    joNodeChannel.insert(DEF_JSON_DATA_SIZE, QString::number(jaRangeFluo.size()));
                    joNodeChannel.insert(DEF_JSON_DATA_VALUE, jaRangeFluo);

                    QString sChannel = QString("%1%2")
                            .arg(DEF_JSON_LED_DATA)
                            .arg(j+1);
                    joNodeHole.insert(sChannel, joNodeChannel);
                }

                QString sHole = QString("%1%2")
                        .arg(DEF_JSON_FLUO_DATA)
                        .arg(QString("%1").arg(i+1, 3, 10, QChar('0')));
                joRootFluo.insert(sHole, joNodeHole);
            }

            // Data serialized
            jdRootFluo.setObject(joRootFluo);
            baJsonData = jdRootFluo.toJson(QJsonDocument::Compact);
#ifdef QT_NO_DEBUG
            qDebug() << Q_FUNC_INFO << QString(baJsonData);
#else
            std::cout << Q_FUNC_INFO << QString(baJsonData).toStdString() << std::endl;
#endif
        }
    }

    qDebug() << Q_FUNC_INFO << nRetVal << mapValue.size() << bTempChart;
    return nRetVal;
}

QString UtilityAPI::convertCmdId2Text(const int nCmdIdx) const
{
    QString sCmdText = "";

    switch (nCmdIdx) {
    case E_TP_SRST: { sCmdText = tr("Reset all"); break; }
    case E_TP_LOAD: { sCmdText = tr("Load cartridge valve"); break; }
    case E_TP_QUIT: { sCmdText = tr("Exit cartridge valve"); break; }
    case E_TP_SLUP: { sCmdText = tr("Magnetic sleeve up valve"); break; }
    case E_TP_SLDO: { sCmdText = tr("Magnetic sleeve down valve"); break; }
    case E_TP_SKUP: { sCmdText = tr("Magnetic rod motor up valve"); break; }
    case E_TP_SKDO: { sCmdText = tr("Magnetic rod motor down valve"); break; }
    case E_TP_LKUP: { sCmdText = tr("Magnetic sleeve magnetic rod up"); break; }
    case E_TP_LKDO: { sCmdText = tr("Magnetic sleeve magnetic rod down"); break; }
    case E_TP_BUBB: { sCmdText = tr("Magnetic sleeve mixing"); break; }
    case E_TP_OMIX: { sCmdText = tr("Magnetic sleeve horizontal mixing"); break; }
    case E_TP_OMAG: { sCmdText = tr("Magnetic rod horizontal filtering"); break; }
    case EM_CSA_DELAY: { sCmdText = tr("Delay"); break; }

    case HT_HA_HTST: { sCmdText = tr("Start lysis"); break; }
    case HT_HA_HTSP: { sCmdText = tr("Stop lysis"); break; }

    case E_PA_ADP_INFO: { sCmdText = tr("ADP info"); break; }
    case E_PA_RUN_STATUS: { sCmdText = tr("run status"); break; }
    case E_PA_TIP_MIXING: { sCmdText = tr("tip mxing"); break; }
    case E_PA_RESET_ADP: { sCmdText = tr("reset ADP"); break; }
    case E_PA_ADP_RESET_STUS: { sCmdText = tr("ADP reset status"); break; }
    case E_PA_1_BACK_SUCK: { sCmdText = tr("first back suck"); break; }
    case E_PA_PROBE_LIQUID: { sCmdText = tr("pressure probe liquid"); break; }
    case E_PA_SUCTION: { sCmdText = tr("suction"); break; }
    case E_PA_2_BACK_SUCK: { sCmdText = tr("second back suck"); break; }
    case E_PA_SPIT: { sCmdText = tr("spit"); break; }
    case E_PA_QUIT_TIP: { sCmdText = tr("quit tip"); break; }
    case E_PA_TIP_STATUS: { sCmdText = tr("tip status"); break; }
    case E_PA_ABS_MOVE: { sCmdText = tr("absolute move"); break; }
    case E_PA_Z_INFO: { sCmdText = tr("Z info"); break; }
    case E_PA_MOVE_STATUS: { sCmdText = tr("move status"); break; }
    case E_PA_Z_RESET: { sCmdText = tr("Z reset"); break; }
    case E_PA_Z_RESET_STUS: { sCmdText = tr("Z reset status"); break; }
    case E_PA_PICKUP_TIP: { sCmdText = tr("pick up tip"); break; }
    default: break;
    }

//    qDebug() << Q_FUNC_INFO << nCmdIdx << sCmdText;
    return sCmdText;
}

int UtilityAPI::convertCmdText2Id(const QString sCmdText) const
{
    int nCmdId = 0;

    if(tr("Reset all") == sCmdText) { nCmdId = E_TP_SRST; goto do_exit; }
    if(tr("Load cartridge valve") == sCmdText) { nCmdId = E_TP_LOAD; goto do_exit; }
    if(tr("Exit cartridge valve") == sCmdText) { nCmdId = E_TP_QUIT; goto do_exit; }
    if(tr("Magnetic sleeve up valve") == sCmdText) { nCmdId = E_TP_SLUP; goto do_exit; }
    if(tr("Magnetic sleeve down valve") == sCmdText) { nCmdId = E_TP_SLDO; goto do_exit; }
    if(tr("Magnetic rod motor up valve") == sCmdText) { nCmdId = E_TP_SKUP; goto do_exit; }
    if(tr("Magnetic rod motor down valve") == sCmdText) { nCmdId = E_TP_SKDO; goto do_exit; }
    if(tr("Magnetic sleeve magnetic rod up") == sCmdText) { nCmdId = E_TP_LKUP; goto do_exit; }
    if(tr("Magnetic sleeve magnetic rod down") == sCmdText) { nCmdId = E_TP_LKDO; goto do_exit; }
    if(tr("Magnetic sleeve mixing") == sCmdText) { nCmdId = E_TP_BUBB; goto do_exit; }
    if(tr("Magnetic sleeve horizontal mixing") == sCmdText) { nCmdId = E_TP_OMIX; goto do_exit; }
    if(tr("Magnetic rod horizontal filtering") == sCmdText) { nCmdId = E_TP_OMAG; goto do_exit; }
    if(tr("Delay") == sCmdText) { nCmdId = EM_CSA_DELAY; goto do_exit; }

    if(tr("Start lysis") == sCmdText) { nCmdId = HT_HA_HTST; goto do_exit; }
    if(tr("Stop lysis") == sCmdText) { nCmdId = HT_HA_HTSP; goto do_exit; }

    if(tr("ADP info") == sCmdText) { nCmdId = E_PA_ADP_INFO; goto do_exit; }
    if(tr("run status") == sCmdText) { nCmdId = E_PA_RUN_STATUS; goto do_exit; }
    if(tr("tip mixing") == sCmdText) { nCmdId = E_PA_TIP_MIXING; goto do_exit; }
    if(tr("reset ADP") == sCmdText) { nCmdId = E_PA_RESET_ADP; goto do_exit; }
    if(tr("ADP reset status") == sCmdText) { nCmdId = E_PA_ADP_RESET_STUS; goto do_exit; }
    if(tr("first back suck") == sCmdText) { nCmdId = E_PA_1_BACK_SUCK; goto do_exit; }
    if(tr("pressure probe liquid") == sCmdText) { nCmdId = E_PA_PROBE_LIQUID; goto do_exit; }
    if(tr("suction") == sCmdText) { nCmdId = E_PA_SUCTION; goto do_exit; }
    if(tr("second back suck") == sCmdText) { nCmdId = E_PA_2_BACK_SUCK; goto do_exit; }
    if(tr("spit") == sCmdText) { nCmdId = E_PA_SPIT; goto do_exit; }
    if(tr("quit tip") == sCmdText) { nCmdId = E_PA_QUIT_TIP; goto do_exit; }
    if(tr("tip status") == sCmdText) { nCmdId = E_PA_TIP_STATUS; goto do_exit; }
    if(tr("absolute move") == sCmdText) { nCmdId = E_PA_ABS_MOVE; goto do_exit; }
    if(tr("Z info") == sCmdText) { nCmdId = E_PA_Z_INFO; goto do_exit; }
    if(tr("move status") == sCmdText) { nCmdId = E_PA_MOVE_STATUS; goto do_exit; }
    if(tr("Z reset") == sCmdText) { nCmdId = E_PA_Z_RESET; goto do_exit; }
    if(tr("Z reset status") == sCmdText) { nCmdId = E_PA_Z_RESET_STUS; goto do_exit; }
    if(tr("pick up tip") == sCmdText) { nCmdId = E_PA_PICKUP_TIP; goto do_exit; }


do_exit:
    qDebug() << Q_FUNC_INFO << sCmdText << nCmdId;
    return nCmdId;
}

QString UtilityAPI::convertStepFlag2Name(const QString sStepFlag) const
{
    QString sStepName = "";


    if(DF_NAME_LOAD_TIPS == sStepFlag) { sStepName = tr("load tipspipe"); }
    else if(DF_NAME_UNLOAD_TIPS == sStepFlag) { sStepName = tr("unload tipspipe"); }
    else if(DF_NAME_PIERCE == sStepFlag) { sStepName = tr("pierce"); }
    else if(DF_NAME_MAG_SLEEVE == sStepFlag) { sStepName = tr("magnetic sleeve"); }
    else if(DF_NAME_MAG_SLEEVE_FAST == sStepFlag) { sStepName = tr("magnetic sleeve (fast)"); }
    else if(DF_NAME_MAG_SLEEVE_SLOW == sStepFlag) { sStepName = tr("magnetic sleeve (slow)"); }
    else if(DF_NAME_TIP_SUCTTION == sStepFlag) { sStepName = tr("tipspipe suction"); }
    else if(DF_NAME_TIP_SPIT == sStepFlag) { sStepName = tr("tipspipe spit"); }
    else if(DF_NAME_TIP_MIXING == sStepFlag) { sStepName = tr("tipspipe mixing"); }
    else if(DF_NAME_TIP_HEATING == sStepFlag) { sStepName = tr("tipspipe heating"); }

//    qDebug() << Q_FUNC_INFO << sStepFlag << sStepName;
    return sStepName;
}

QString UtilityAPI::getErrorLevelByType(int nErrLevel)
{
    qDebug() << Q_FUNC_INFO << nErrLevel;
    QString sErrLevel;

    switch (nErrLevel) {
    case E_ERR_DEBUG:
        sErrLevel = tr("debug");
        break;
    case E_ERR_TRACE:
        sErrLevel = tr("trace");
        break;
    case E_ERR_WARNING:
        sErrLevel = tr("warining");
        break;
    case E_ERR_EXCEPTION:
        sErrLevel = tr("exception");
        break;
    case E_ERR_ERROR:
        sErrLevel = tr("error");
        break;
    case E_ERR_CRITICAL:
        sErrLevel = tr("critical");
        break;
    case E_ERR_ALERT:
        sErrLevel = tr("alert");
        break;
    case E_ERR_EMERG:
        sErrLevel = tr("emerg");
        break;
    case E_ERR_OFF:
        sErrLevel = tr("off");
        break;
    default:
        sErrLevel = "-";
        break;
    }

    qDebug() << Q_FUNC_INFO << sErrLevel;
    return sErrLevel;
}

QString UtilityAPI::getErrorDescByCodeInspect(long nErrNum)
{
    qDebug() << Q_FUNC_INFO << nErrNum;

    QString sErrDesc;

    // Devie inspect error code
    switch (nErrNum) {
    case DEF_ERR_IAP_275:   { sErrDesc = tr("The current motor action is not completed and is in busy state"); break; }
    case DEF_ERR_IAP_276:   { sErrDesc = tr("motor action time out"); break; }
    case DEF_ERR_IAP_277:   { sErrDesc = tr("motor position error"); break; }
    case DEF_ERR_IAP_278:   { sErrDesc = tr("motor param error"); break; }
    case DEF_ERR_IAP_279:   { sErrDesc = tr("motor command error"); break; }
    case DEF_ERR_IAP_280:   { sErrDesc = tr("motor param set error"); break; }
    case DEF_ERR_IAP_281:   { sErrDesc = tr("motor param save error"); break; }
    case DEF_ERR_IAP_282:   { sErrDesc = tr("motor param get error"); break; }
    case DEF_ERR_IAP_283:   { sErrDesc = tr("motor compensation load error"); break; }
    case DEF_ERR_IAP_284:   { sErrDesc = tr("motor param load error"); break; }
    case DEF_ERR_IAP_285:   { sErrDesc = tr("motor loss step of A"); break; }
    case DEF_ERR_IAP_286:   { sErrDesc = tr("motor loss step of B"); break; }
    case DEF_ERR_IAP_287:   { sErrDesc = tr("motor over current"); break; }
    case DEF_ERR_IAP_288:   { sErrDesc = tr("motor under voltage"); break; }
    case DEF_ERR_IAP_290:   { sErrDesc = tr("motor clear param error"); break; }
    case DEF_ERR_IAP_291:   { sErrDesc = tr("optical motor reset position error"); break; }
    case DEF_ERR_IAP_292:   { sErrDesc = tr("card hori motor action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_293:   { sErrDesc = tr("magnet sleeve action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_294:   { sErrDesc = tr("magnet stick motor action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_295:   { sErrDesc = tr("card rota motor action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_296:   { sErrDesc = tr("heat hori motor action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_297:   { sErrDesc = tr("heat vert motor action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_298:   { sErrDesc = tr("card hori motor action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_299:   { sErrDesc = tr("magnet sleeve action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_300:   { sErrDesc = tr("magnet stick motor action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_301:   { sErrDesc = tr("card rota motor action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_302:   { sErrDesc = tr("heat hori motor action time out  when the whole machine is reset"); break; }
    case DEF_ERR_IAP_303:   { sErrDesc = tr("heat vert motor action time out  when the whole machine is reset"); break; }

    case 1025:   { sErrDesc = tr("The temperature difference between the two sensors is abnormal"); break; }
    case 1026:   { sErrDesc = tr("The temperature value collected is not in the range of temperature setting"); break; }
    case 1027:   { sErrDesc = tr("The temperature value by the NO.1 sensor collected is not in the range of temperature setting"); break; }
    case 1028:   { sErrDesc = tr("The temperature value by the NO.2 sensor collected is not in the range of temperature setting"); break; }
    case 1029:   { sErrDesc = tr("The temperature value by the NO.3 sensor collected is not in the range of temperature setting"); break; }
    default:                { sErrDesc = tr("Undefined error"); break; }
    }

    qDebug() << Q_FUNC_INFO << sErrDesc;
    return sErrDesc;
}

float UtilityAPI::getTimeConsuming()
{
    return m_fTimeConsuming;
}

int UtilityAPI::_insertFlowAction(QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, ST_MOTOR_STATE &stMotorState, int nCmdIdx, int nStepIdx, int nCmdID, float fParam, float fParam2, float fParam3, float fParam4)
{
    int nRetVal = 0;

    if(0 > fParam) fParam = 0;
    switch (nCmdID) {
    case E_PA_Z_RESET:
        stMotorState.nPipetteOffset = 0;
        stMotorState.nSuctionSpitOffset = MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY;
        stMotorState.fTimeConsuming += 10.0;
        break;
    case E_TP_SRST:
        stMotorState.nTrayOffset = 0;
        stMotorState.nMagenticRodOffset = 0;
        stMotorState.nMagenticSleeveOffset = 0;
        stMotorState.fTimeConsuming += 10.0;
        break;
    case E_TP_LOAD:
        stMotorState.nTrayOffset -= abs(fParam);
        stMotorState.fTimeConsuming += 2.0;
        break;
    case E_TP_QUIT:
        stMotorState.nTrayOffset += abs(fParam);
        stMotorState.fTimeConsuming += 2.0;
        break;
    case E_TP_SLUP:
        stMotorState.nMagenticSleeveOffset -= abs(fParam);
        stMotorState.fTimeConsuming += 5.0;
        break;
    case E_TP_SLDO:
        stMotorState.nMagenticSleeveOffset += abs(fParam);
        stMotorState.fTimeConsuming += 5.0;
        break;
    case E_TP_SKUP:
        stMotorState.nMagenticRodOffset -= abs(fParam);
        stMotorState.fTimeConsuming += 5.0;
        break;
    case E_TP_SKDO:
        stMotorState.nMagenticRodOffset += abs(fParam);
        stMotorState.fTimeConsuming += 5.0;
        break;
    case E_TP_LKUP:
        stMotorState.nMagenticRodOffset -= abs(fParam);
        stMotorState.nMagenticSleeveOffset -= abs(fParam);
        stMotorState.fTimeConsuming += 5.0;
        break;
    case E_TP_LKDO:
        stMotorState.nMagenticRodOffset += abs(fParam);
        stMotorState.nMagenticSleeveOffset += abs(fParam);

        stMotorState.fTimeConsuming += 5.0;
        if(0 < fParam3) stMotorState.fTimeConsuming += (abs(fParam3 / (float)1000) + 10);
        break;
    case E_TP_OMAG:
        stMotorState.nMagenticRodOffset += abs(fParam);
        stMotorState.nMagenticSleeveOffset += abs(fParam);

        if(0 < fParam4) stMotorState.fTimeConsuming += (abs(fParam4 / (float)1000) + 10);
        break;
    case E_PA_SUCTION:
        if(MotorVars_T1C::nPierceRemainOffsetY <= stMotorState.nSuctionSpitOffset) {
            if(MotorVars_T1C::nTipsMaxVolume >= (stMotorState.nSuctionSpitOffset - MotorVars_T1C::nPierceRemainOffsetY) + abs(fParam)) {
                stMotorState.nSuctionSpitOffset += abs(fParam);
            }
            else {
                fParam = qMax(0, MotorVars_T1C::nTipsMaxVolume - (stMotorState.nSuctionSpitOffset - MotorVars_T1C::nPierceRemainOffsetY));
                stMotorState.nSuctionSpitOffset = MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY;
                nRetVal = -10;
            }
        }
        else {
            fParam = 0;
            nRetVal = -11;
        }

        stMotorState.fTimeConsuming += 15.0;
        break;
    case E_PA_SUCTION_2:
            // Piercing or Unload tips
            nCmdID = E_PA_SUCTION;

            if(MotorVars_T1C::nTipsMaxVolume > (stMotorState.nSuctionSpitOffset - MotorVars_T1C::nPierceRemainOffsetY) + abs(fParam)) {
                stMotorState.nSuctionSpitOffset += abs(fParam);
            }
            else {
                fParam = qMax(0, MotorVars_T1C::nTipsMaxVolume - (stMotorState.nSuctionSpitOffset - MotorVars_T1C::nPierceRemainOffsetY));
                stMotorState.nSuctionSpitOffset = MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY;
                nRetVal = -20;
            }

            stMotorState.fTimeConsuming += 15.0;
            break;
    case E_PA_SPIT:
        if(MotorVars_T1C::nPierceRemainOffsetY < stMotorState.nSuctionSpitOffset) {
            if(0 <= (stMotorState.nSuctionSpitOffset - MotorVars_T1C::nPierceRemainOffsetY) - abs(fParam)) {
                stMotorState.nSuctionSpitOffset -= abs(fParam);
            }
            else {
                fParam = qMax(0, stMotorState.nSuctionSpitOffset - MotorVars_T1C::nPierceRemainOffsetY);
                stMotorState.nSuctionSpitOffset = MotorVars_T1C::nPierceRemainOffsetY;
                nRetVal = -30;
            }
        }
        else {
            fParam = 0;
            nRetVal = -31;
        }

        stMotorState.fTimeConsuming += 15.0;
        break;
    case E_PA_SPIT_2:
            // Piercing or Unload tips
            nCmdID = E_PA_SPIT;

            if(0 < stMotorState.nSuctionSpitOffset - abs(fParam)) {
                stMotorState.nSuctionSpitOffset -= abs(fParam);
            }
            else {
                fParam = qMax(0, stMotorState.nSuctionSpitOffset);
                stMotorState.nSuctionSpitOffset = 0;
                nRetVal = -40;
            }

            stMotorState.fTimeConsuming += 15.0;
            break;
    case E_PA_PICKUP_TIP:
        stMotorState.nPipetteOffset += abs(fParam);
        stMotorState.fTimeConsuming += 2.0;
        break;
    case E_PA_ABS_MOVE:
        stMotorState.nPipetteOffset = abs(fParam);
        stMotorState.fTimeConsuming += 2.0;
        break;
    case EM_CSA_DELAY:
        stMotorState.fTimeConsuming += abs(fParam / (float)1000);
        break;
    case E_TP_BUBB:
        if(0 < fParam2) stMotorState.fTimeConsuming += abs(fParam2 / (float)1000);
        break;
    case E_TP_OMIX:
        if(0 < fParam3) stMotorState.fTimeConsuming += abs(fParam3 / (float)1000);
        break;
    default:
        break;
    }

    if(0 < nCmdID) {
        // Push commands into queue
        ST_FLOW_PROC_ACTION stFlowProcAction;
        stFlowProcAction.nIdx = nCmdIdx;
        stFlowProcAction.nStepIdx = nStepIdx;
        stFlowProcAction.nCmdID = nCmdID;
        stFlowProcAction.fParam = fParam;
        stFlowProcAction.fParam2 = fParam2;
        stFlowProcAction.fParam3 = fParam3;
        stFlowProcAction.fParam4 = fParam4;

        stFlowProcAction.stMotorState = stMotorState;

        queFlowProcAction << stFlowProcAction;

        // Construct the string of action commands
        QString sAction = QString("%1").arg(nCmdID);
        if(-1 != fParam) {
            sAction = QString("%1,%2").arg(sAction).arg(fParam);

            if(-1 != fParam2) {
                sAction = QString("%1,%2").arg(sAction).arg(fParam2);

                if(-1 != fParam3) {
                    sAction = QString("%1,%2").arg(sAction).arg(fParam3);

                    if(-1 != fParam4) {
                        sAction = QString("%1,%2").arg(sAction).arg(fParam4);
                    }
                }
            }
        }

        sAction = QString("%1%2").arg(sAction).arg(";");
        lstAction << sAction;
    }
    else {
        nRetVal = -1;
    }

    qDebug() << Q_FUNC_INFO << nRetVal << nCmdIdx << nStepIdx << nCmdID << fParam << fParam2 << fParam3 << fParam4
             << stMotorState.nTrayOffset << stMotorState.nMagenticRodOffset << stMotorState.nMagenticSleeveOffset
             << stMotorState.nPipetteOffset << stMotorState.nSuctionSpitOffset << stMotorState.fTimeConsuming;
    return nRetVal;
}

int UtilityAPI::_getHoleAbsoluteOffset(int nKitType, int nTarHoleNumber, bool bPCR)
{
    int nDistance = 0;
    int nHoleOffset = (E_EK_T1C_HOLE_10 == nKitType) ? 1 : 0;

    if(!bPCR) {
        if(0 < nTarHoleNumber && DF_EXT_HOLE_AMOUNT - nHoleOffset >= nTarHoleNumber) {
            for(int i=0; i<DF_EXT_HOLE_AMOUNT - nHoleOffset - nTarHoleNumber; i++) {
                nDistance += ((E_EK_T1C_HOLE_10 == nKitType) ? gs_nExtHoleSpacing10[i] : gs_nExtHoleSpacing11[i]);

                qDebug() << Q_FUNC_INFO << i << ((E_EK_T1C_HOLE_10 == nKitType) ? gs_nExtHoleSpacing10[i] : gs_nExtHoleSpacing11[i]) << nDistance;
            }
        }
    }
    else {
        if(0 < nTarHoleNumber && DF_PCR_HOLE_AMOUNT - 0 >= nTarHoleNumber) {
            for(int i=0; i<nTarHoleNumber - 1; i++) {
                nDistance += gs_nPcrHoleSpacing[i];

                qDebug() << Q_FUNC_INFO << i << gs_nPcrHoleSpacing[i] << nDistance;
            }
        }
    }


    qDebug() << Q_FUNC_INFO << nKitType << nTarHoleNumber << bPCR << nDistance;
    return nDistance;
}

void UtilityAPI::_resetMotors(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, bool bInit)
{
    qDebug() << Q_FUNC_INFO << queFlowProcAction.size() << lstAction.size() << nCmdIdx << nStepIdx << bInit;

    if(bInit) {
        // Reset Z-motor
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_Z_RESET);

        // Reset all
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SRST);
    }
    else {
        // Move the suction & spit motor to its initial position
        if(MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY > stMotorState.nSuctionSpitOffset) {
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_SUCTION_2, MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY - stMotorState.nSuctionSpitOffset);
        }

        // Move the pipette motor to its initial position
        if(0 < stMotorState.nPipetteOffset) {
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, MotorVars_T1C::nResetTipsOffsetY2);
        }

        // Move the magentic rod motor to its initial position
        if(0 < stMotorState.nMagenticRodOffset) {
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SKUP, stMotorState.nMagenticRodOffset);
        }

        // Move the magentic sleeve motor to its initial position
        if(0 < stMotorState.nMagenticSleeveOffset) {
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SLUP, stMotorState.nMagenticSleeveOffset);
        }

        // Move the tray motor to its initial position
        if(0 < stMotorState.nTrayOffset) {
            // Load cartridge & Locate to the sleeve hole
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_LOAD, stMotorState.nTrayOffset);
        }
    }
}

void UtilityAPI::_switchDrivepipe(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, bool bEnable)
{
    // Move the tray motor to the position of sleeve hole
    _moveTrayMotorForPipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, E_MT_SLEEVE);

    if(bEnable) {
        // Drivepipe is graped
        stMotorState.bDrivepipeGraped = true;

        // Magnetic sleeve magnetic rod down
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SLDO, MotorVars_T1C::nGrapSleeveOffsetY, MotorVars_T1C::nGrapSleeveOffsetY2);

        // Magnetic sleeve magnetic rod up
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SLUP, MotorVars_T1C::nGrapSleeveOffsetY);
    }
    else {
        // Magnetic sleeve magnetic rod down
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_LKDO, MotorVars_T1C::nMixingSleeveOffsetY);

        // Magnetic sleeve up
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SLUP, MotorVars_T1C::nDiscardSleeveOffsetY);

        // Magnetic rod motor up
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SKUP, MotorVars_T1C::nMixingSleeveOffsetY);

        // Magnetic sleeve up
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SLUP, MotorVars_T1C::nMixingSleeveOffsetY - MotorVars_T1C::nDiscardSleeveOffsetY);

        // Drivepipe isn't graped
        stMotorState.bDrivepipeGraped = false;
    }
}

void UtilityAPI::_switchTipspipe(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, bool bEnable)
{
    qDebug() << Q_FUNC_INFO << queFlowProcAction.size() << lstAction.size() << nCmdIdx << nStepIdx << bEnable;

    // Move the tray motor to the position of tip hole
    _moveTrayMotorForPipe(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, E_MT_TIPS);

    if(bEnable) {
        int nMotorSteps = 102400;

        // Tipspipe is graped
        stMotorState.bTipspipeGraped = true;

        // Move downwards at a vertical reserve space
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_SPIT, MotorVars_T1C::nTipsMaxVolume);

        // Move downwards Catch a tipspile
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_PICKUP_TIP, MotorVars_T1C::nGrapTipsOffsetY, nMotorSteps);

        // Catch the tipspile & move upwards
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, MotorVars_T1C::nResetTipsOffsetY2);
    }
    else {
        // Move downwards at the bottom of tipspipe
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, MotorVars_T1C::nDiscardTipsOffsetY);

        // Move downwards at a vertical reserve space (Discard the tipspile)
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_SPIT_2, MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY);

        // Move upwards at a vertical reserve space (Restore piercing head)
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_SUCTION_2, MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY);

        // Move upwards
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, MotorVars_T1C::nResetTipsOffsetY2);

        // Tipspipe isn't graped
        stMotorState.bTipspipeGraped = false;
    }
}

void UtilityAPI::_doPiercing(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, const ST_MOTION_INFO &stMotionInfo, int nKitType)
{
    qDebug() << Q_FUNC_INFO << queFlowProcAction.size() << lstAction.size() << nCmdIdx << nStepIdx << nKitType;

    // Move the tray to target holes
    _moveTrayMotorForAction(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nKitType);

    if(DF_EXT_A_HOLE_AMOUNT < stMotionInfo.nTarHoleNumber && DF_EXT_B_HOLE_AMOUNT > stMotionInfo.nTarHoleNumber) {
        // Move downwards at a vertical reserve space
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_SPIT_2, MotorVars_T1C::nTipsMaxVolume + MotorVars_T1C::nPierceRemainOffsetY);

        // Move motor downwards before piercing
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, MotorVars_T1C::nPierceOffsetY);

        // move motor upwards after piercing
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, MotorVars_T1C::nPierceOffsetY2);

        // Move upwards at a vertical reserve space
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_SUCTION_2, MotorVars_T1C::nPierceRemainOffsetY);
    }
}

void UtilityAPI::_doSilent(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, const ST_MOTION_INFO &stMotionInfo, int nDuration, int nKitType)
{
    qDebug() << Q_FUNC_INFO << queFlowProcAction.size() << lstAction.size() << nCmdIdx << nStepIdx << nKitType;

    // Move the tray to target holes
    _moveTrayMotorForAction(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nKitType);

    if(DF_EXT_A_HOLE_AMOUNT < stMotionInfo.nTarHoleNumber && DF_EXT_HOLE_AMOUNT > stMotionInfo.nTarHoleNumber) {
        if(0 < nDuration) _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, EM_CSA_DELAY, nDuration);
    }
}

void UtilityAPI::_doSleelveMixing(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, const ST_MOTION_INFO &stMotionInfo, int nDuration, int nMotorSpeed, int nKitType, int nVolumeValue)
{
    qDebug() << Q_FUNC_INFO << queFlowProcAction.size() << lstAction.size() << nCmdIdx << nStepIdx << nDuration << nMotorSpeed << nKitType << nVolumeValue;

    // Move the tray to target holes
    _moveTrayMotorForAction(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nKitType);

    // Magnetic sleeve mixing
    if(DF_EXT_A_HOLE_AMOUNT < stMotionInfo.nTarHoleNumber && DF_EXT_HOLE_AMOUNT > stMotionInfo.nTarHoleNumber) {
        int nMotorSteps = MotorVars_T1C::nMagneticMoveOffsetY;
        int nMoveRange = (E_EK_T1C_HOLE_10 == nKitType) ? MotorVars_T1C::nMagnetiMoveOffsetX : MotorVars_T1C::nMagnetiMoveOffsetX2;

        if(E_EK_T1C_HOLE_10 == nKitType) {
            if(2 == stMotionInfo.nTarHoleNumber) {
                if(4000 > nVolumeValue) nMotorSteps = 0;
            }
            else {
                if(500 > nVolumeValue) nMotorSteps = 0;
            }
        }
        else {
            if(2 == stMotionInfo.nTarHoleNumber) {
                if(2000 > nVolumeValue) nMotorSteps = 0;
            }
            else {
                if(500 > nVolumeValue) nMotorSteps = 0;
            }
        }

        // Magnetic sleeve magnetic rod down
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_LKDO, MotorVars_T1C::nMixingSleeveOffsetY);

        // Magnetic rod motor up
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SKUP, MotorVars_T1C::nMixingSleeveOffsetY);

        if(2 == stMotionInfo.nTarHoleNumber) {
            // Start drivepipe mixing
            stMotorState.bDrivepipeMixing2 = true;

            // Magnetic sleeve mixing
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_OMIX, nMotorSteps, nMoveRange, nDuration, nMotorSpeed);

            // Stop drivepipe mixing
            stMotorState.bDrivepipeMixing2 = false;
        }
        else {
            // Start drivepipe mixing
            stMotorState.bDrivepipeMixing = true;

            // Magnetic sleeve mixing
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_BUBB, nMotorSteps, nDuration, nMotorSpeed);

            // Stop drivepipe mixing
            stMotorState.bDrivepipeMixing = false;
        }

        // Magnetic sleeve up
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_SLUP, MotorVars_T1C::nMixingSleeveOffsetY);
    }
}

void UtilityAPI::_doMagnetFiltering(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, const ST_MOTION_INFO &stMotionInfo, int nDuration, int nKitType, int nVolumeValue)
{
    qDebug() << Q_FUNC_INFO << queFlowProcAction.size() << lstAction.size() << nCmdIdx << nStepIdx << nDuration << nKitType << nVolumeValue;

    // Move the tray to target holes
    _moveTrayMotorForAction(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nKitType);

    if(DF_EXT_A_HOLE_AMOUNT < stMotionInfo.nTarHoleNumber && DF_EXT_HOLE_AMOUNT > stMotionInfo.nTarHoleNumber) {
        int nMotorSteps = MotorVars_T1C::nMagneticMoveOffsetY;
        int nMoveRange = (0 == nKitType) ? MotorVars_T1C::nMagnetiMoveOffsetX : MotorVars_T1C::nMagnetiMoveOffsetX2;

        double dHoleHeight = 53.15 * 1.0;
        double dCompensation = 0.0;
        double dEmptyHeight = 0;
        double dLiquidHeight = 0;

        if(1000 <= nVolumeValue) dCompensation += 5.0;
        else if(200 >= nVolumeValue) dCompensation -= 5.0;

        if(E_EK_T1C_HOLE_10 == nKitType) {
            if(2 == stMotionInfo.nTarHoleNumber) {
                double dTotalVolume = 18298.233 * 1.0;

                dEmptyHeight = (dTotalVolume - nVolumeValue) / 347.439;
                dLiquidHeight = dHoleHeight - dEmptyHeight + dCompensation;
            }
            else {
                double dTotalVolume = 5000.390 * 1.0;

                dEmptyHeight = (dTotalVolume - nVolumeValue) / 109.857;
                dLiquidHeight = dHoleHeight - dEmptyHeight + dCompensation;
            }
        }
        else {
            if(2 == stMotionInfo.nTarHoleNumber) {
                double dTotalVolume = 9352.850 * 1.0;

                dEmptyHeight = (dTotalVolume - nVolumeValue) / 177.879;
                dLiquidHeight = dHoleHeight - dEmptyHeight + dCompensation;
            }
            else {
                double dTotalVolume = 5000.390 * 1.0;

                dEmptyHeight = (dTotalVolume - nVolumeValue) / 109.857;
                dLiquidHeight = dHoleHeight - dEmptyHeight + dCompensation;
            }
        }

        nMotorSteps = dLiquidHeight / 0.04 * 128;
        qDebug() << Q_FUNC_INFO << dEmptyHeight << dLiquidHeight << nMotorSteps;

        if(2 == stMotionInfo.nTarHoleNumber) {
            int nOffsetV = 1000;

            // Magnetic sleeve magnetic rod down
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_LKDO, MotorVars_T1C::nMagneticMaxOffsetY - nMotorSteps);

            // Magnetic sleeve horizontal filtering
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_OMAG, nMotorSteps, abs(nMotorSteps - nOffsetV), nMoveRange, nDuration);
        }
        else {
            // Magnetic sleeve magnetic rod down
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_LKDO, MotorVars_T1C::nMagneticMaxOffsetY, nMotorSteps, nDuration);
        }

        // Magnetic sleeve magnetic rod up
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_LKUP, MotorVars_T1C::nMagneticMaxOffsetY, nMotorSteps);
    }
}

void UtilityAPI::_doTipSuctionSpit(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, const ST_MOTION_INFO &stMotionInfo, int nVolumeValue, bool bSuction, int nKitType)
{
    qDebug() << Q_FUNC_INFO << queFlowProcAction.size() << lstAction.size() << nCmdIdx << nStepIdx << nVolumeValue << bSuction << nKitType;

    // Move the motros to target holes
    _moveTrayMotorForAction(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nKitType);

    bool bHoleNumberValid = false;
    if(E_AT_SPECIMEN == stMotionInfo.nCurAreaType) {
        bHoleNumberValid = 0 < stMotionInfo.nTarHoleNumber && DF_EXT_A_HOLE_AMOUNT >= stMotionInfo.nTarHoleNumber;
    }
    else if(E_AT_EXTRACT == stMotionInfo.nCurAreaType) {
        bHoleNumberValid = 0 < stMotionInfo.nTarHoleNumber && DF_EXT_HOLE_AMOUNT >= stMotionInfo.nTarHoleNumber;
    }
    else if(E_AT_REACTION == stMotionInfo.nCurAreaType) {
        bHoleNumberValid = 0 < stMotionInfo.nTarHoleNumber && DF_PCR_HOLE_AMOUNT >= stMotionInfo.nTarHoleNumber;
    }

    // Tipspipe suction or spit
    if(bHoleNumberValid) {
        int nSpitOffsetY = 0;

        // Get the value of spit offset
        if(E_AT_REACTION == stMotionInfo.nCurAreaType) {
            nSpitOffsetY = (bSuction ? MotorVars_T1C::nPcrSwallowOffsetY : MotorVars_T1C::nPcrSpitOffsetY);
        }
        else {
            nSpitOffsetY = (bSuction ? MotorVars_T1C:: nExtSwallowOffsetY : MotorVars_T1C:: nExtSpitOffsetY);
        }

        // Move downwards at the bottom of tipspipe
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, nSpitOffsetY);

        int nVolume = qMin(MotorVars_T1C::nTipsMaxVolume, nVolumeValue);
        if(0 < nVolume) {
            int nCmdID = bSuction ? E_PA_SUCTION : E_PA_SPIT;
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, nCmdID, nVolume);
        }

        // Move upwards
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, 0);
    }
}

void UtilityAPI::_doTipMixing(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, const ST_MOTION_INFO &stMotionInfo, int nVolumeValue, int nCycles, int nKitType)
{
    qDebug() << Q_FUNC_INFO << queFlowProcAction.size() << lstAction.size() << nCmdIdx << nStepIdx << nVolumeValue << nCycles << nKitType;

    // Move the motors to target holes
    _moveTrayMotorForAction(stMotorState, queFlowProcAction, lstAction, nCmdIdx, nStepIdx, stMotionInfo, nKitType);

    bool bHoleNumberValid = false;
    if(E_AT_SPECIMEN == stMotionInfo.nCurAreaType) {
        bHoleNumberValid = 0 < stMotionInfo.nTarHoleNumber && DF_EXT_A_HOLE_AMOUNT >= stMotionInfo.nTarHoleNumber;
    }
    else if(E_AT_EXTRACT == stMotionInfo.nCurAreaType) {
        bHoleNumberValid = 0 < stMotionInfo.nTarHoleNumber && DF_EXT_HOLE_AMOUNT > stMotionInfo.nTarHoleNumber;
    }

    // Tipspipe mixing
    if(bHoleNumberValid) {
        int nVolume = qMin(MotorVars_T1C::nTipsMaxVolume, nVolumeValue);

        // Move downwards at a vertical reserve space
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_SPIT, MotorVars_T1C::nTipsMaxVolume);

        // Move downwards at the bottom of tipspipe
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, MotorVars_T1C::nTipsMixingOffsetY);

        // Start tipspipe mixing
        stMotorState.bTipspipeMixing = true;

        // Move upwards at a vertical reserve space
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_TIP_MIXING, nVolume, nCycles);

        // Stop tipspipe mixing
        stMotorState.bTipspipeMixing = false;

        // Move upwards
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_PA_ABS_MOVE, 0);
    }
}

void UtilityAPI::_switchHeating(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, int nHoleNumber, int nKitType, bool bEnable, float fHeatValue)
{
    qDebug() << Q_FUNC_INFO << queFlowProcAction.size() << lstAction.size() << nCmdIdx << nStepIdx << nHoleNumber << nKitType << bEnable << fHeatValue;

    int nHeatingChannelIdx = -1;

    if(E_EK_T1C_HOLE_10 == nKitType) {
        if(bEnable) {
            switch (nHoleNumber) {
            case 1: { nHeatingChannelIdx = 0; break; }
            case 4: { nHeatingChannelIdx = 2; break; }
            case 9: { nHeatingChannelIdx = 3; break; }
            default: { break; }
            }

            if(0 < fHeatValue) {
                _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, HT_HA_HTST, nHeatingChannelIdx, fHeatValue);

                if(1 == nHoleNumber) {
                    _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, HT_HA_HTST, nHeatingChannelIdx + 1, fHeatValue);
                }
            }
        }
        else {
            if(0 < nHoleNumber) {
                switch (nHoleNumber) {
                case 1: { nHeatingChannelIdx = 0; break; }
                case 4: { nHeatingChannelIdx = 2; break; }
                case 9: { nHeatingChannelIdx = 3; break; }
                default: { return; }
                }

                _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, HT_HA_HTSP, nHeatingChannelIdx);

                if(1 == nHoleNumber) {
                    _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, HT_HA_HTSP, nHeatingChannelIdx + 1);
                }
            }
            else {
                _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, HT_HA_HTSP);
            }
        }
    }
    else if(E_EK_T1C_HOLE_11 == nKitType) {
        if(bEnable) {
            switch (nHoleNumber) {
            case 1: { nHeatingChannelIdx = 0; break; }
            case 2: { nHeatingChannelIdx = 1; break; }
            case 5: { nHeatingChannelIdx = 2; break; }
            case 10: { nHeatingChannelIdx = 3; break; }
            default: { break; }
            }

            if(0 < fHeatValue) {
                _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, HT_HA_HTST, nHeatingChannelIdx, fHeatValue);
            }
        }
        else {
            if(0 < nHoleNumber) {
                switch (nHoleNumber) {
                case 1: { nHeatingChannelIdx = 0; break; }
                case 2: { nHeatingChannelIdx = 1; break; }
                case 5: { nHeatingChannelIdx = 2; break; }
                case 10: { nHeatingChannelIdx = 3; break; }
                default: { return; }
                }

                _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, HT_HA_HTSP, nHeatingChannelIdx);
            }
            else {
                _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, HT_HA_HTSP);
            }
        }
    }
}

int UtilityAPI::_getAdjacentActionInfo(ST_MOTION_INFO &stMotionInfo, const QList<ST_STEP_INFO> &lstModelData, int nRowIdx)
{
    int nRetVal = 0;

    stMotionInfo.clear();

    if(0 < lstModelData.size()) {
        // ***************** Loop lookup the list of steps *****************
        for(int i=0, j = 0; i<lstModelData.size(); i++, j++) {
            if(nRowIdx == i) {
                // Previous action info
                if(0 < i) {
                    if(DF_NAME_MAG_SLEEVE == lstModelData.at(i - 1).sStepName
                            || DF_NAME_MAG_SLEEVE_FAST == lstModelData.at(i - 1).sStepName
                            || DF_NAME_MAG_SLEEVE_SLOW == lstModelData.at(i - 1).sStepName) {
                        stMotionInfo.nPreMoveType = E_MT_SLEEVE;
                    }
                    else if(DF_NAME_PIERCE == lstModelData.at(i - 1).sStepName) {
                        stMotionInfo.nPreMoveType = E_MT_PIERCING;
                    }
                    else if(DF_NAME_LOAD_TIPS == lstModelData.at(i - 1).sStepName
                            || DF_NAME_UNLOAD_TIPS == lstModelData.at(i - 1).sStepName
                            || DF_NAME_TIP_SUCTTION == lstModelData.at(i - 1).sStepName
                            || DF_NAME_TIP_SPIT == lstModelData.at(i - 1).sStepName
                            || DF_NAME_TIP_MIXING == lstModelData.at(i - 1).sStepName
                            || DF_NAME_TIP_HEATING == lstModelData.at(i - 1).sStepName) {
                        stMotionInfo.nPreMoveType = E_MT_TIPS;
                    }

                    if(DF_AREA_SPECIMEN == lstModelData.at(i - 1).sHoleType.trimmed()) {
                        stMotionInfo.nPreAreaType = E_AT_SPECIMEN;
                        stMotionInfo.nSrcHoleNumber = (1 > lstModelData.at(i - 1).nHoleNumber) ? 1 : lstModelData.at(i - 1).nHoleNumber;
                    }
                    else if(DF_AREA_REACTION == lstModelData.at(i - 1).sHoleType.trimmed()) {
                        stMotionInfo.nPreAreaType = E_AT_REACTION;
                        stMotionInfo.nSrcHoleNumber = (1 > lstModelData.at(i - 1).nHoleNumber) ? 1 : lstModelData.at(i - 1).nHoleNumber;
                    }
                    else {
                        stMotionInfo.nPreAreaType = E_AT_EXTRACT;
                        stMotionInfo.nSrcHoleNumber = ((1 > lstModelData.at(i - 1).nHoleNumber) ? 1 : lstModelData.at(i - 1).nHoleNumber) + DF_EXT_A_HOLE_AMOUNT;
                    }
                }
                else {
                    stMotionInfo.nPreMoveType = E_MT_NONE;
                    stMotionInfo.nPreAreaType = E_AT_NONE;
                    stMotionInfo.nSrcHoleNumber = DF_EXT_HOLE_AMOUNT;
                }

                // Current action info
                if(DF_NAME_MAG_SLEEVE == lstModelData.at(i).sStepName
                        || DF_NAME_MAG_SLEEVE_FAST == lstModelData.at(i).sStepName
                        || DF_NAME_MAG_SLEEVE_SLOW == lstModelData.at(i).sStepName) {
                    stMotionInfo.nCurMoveType = E_MT_SLEEVE;
                }
                else if(DF_NAME_PIERCE == lstModelData.at(i).sStepName) {
                    stMotionInfo.nCurMoveType = E_MT_PIERCING;
                }
                else if(DF_NAME_LOAD_TIPS == lstModelData.at(i).sStepName
                        || DF_NAME_UNLOAD_TIPS == lstModelData.at(i).sStepName
                        || DF_NAME_TIP_SUCTTION == lstModelData.at(i).sStepName
                        || DF_NAME_TIP_SPIT == lstModelData.at(i).sStepName
                        || DF_NAME_TIP_MIXING == lstModelData.at(i).sStepName
                        || DF_NAME_TIP_HEATING == lstModelData.at(i).sStepName) {
                    stMotionInfo.nCurMoveType = E_MT_TIPS;
                }

                if(DF_AREA_SPECIMEN == lstModelData.at(i).sHoleType.trimmed()) {
                    stMotionInfo.nCurAreaType = E_AT_SPECIMEN;
                    stMotionInfo.nTarHoleNumber = lstModelData.at(i).nHoleNumber;
                }
                else if(DF_AREA_REACTION == lstModelData.at(i).sHoleType.trimmed()) {
                    stMotionInfo.nCurAreaType = E_AT_REACTION;
                    stMotionInfo.nTarHoleNumber = lstModelData.at(i).nHoleNumber;
                }
                else {
                    stMotionInfo.nCurAreaType = E_AT_EXTRACT;
                    stMotionInfo.nTarHoleNumber = lstModelData.at(i).nHoleNumber + DF_EXT_A_HOLE_AMOUNT;
                }

                nRetVal = i + 1;
                break;
            }
        }
    }
    else {
        nRetVal = -1;
    }

    qDebug() << Q_FUNC_INFO << nRetVal << stMotionInfo.nPreMoveType << stMotionInfo.nPreAreaType << stMotionInfo.nSrcHoleNumber << stMotionInfo.nCurMoveType << stMotionInfo.nCurAreaType << stMotionInfo.nTarHoleNumber;
    return nRetVal;
}

bool UtilityAPI::_moveTrayMotorForPipe(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, int nActionType)
{
    bool bMoveTray = false;

    qDebug() << Q_FUNC_INFO << queFlowProcAction.size() << lstAction.size() << nCmdIdx << nStepIdx << nActionType;

    int nGrapPipeOffset = 0;

    switch (nActionType) {
    case E_MT_SLEEVE:
        // Drivepipe
        nGrapPipeOffset = MotorVars_T1C::nGrapSleeveOffsetX;
        break;
    case E_MT_TIPS:
        // Tipspipe
        nGrapPipeOffset = MotorVars_T1C::nGrapTipsOffsetX;
        break;
    default:
        return false;
    }

    // Move the tray motor to the position of sleeve hole
    int nMoveOffset = 0;
    if(nGrapPipeOffset != stMotorState.nTrayOffset) {
        nMoveOffset = stMotorState.nTrayOffset - nGrapPipeOffset;
        if(0 < nMoveOffset) {
            // Load cartridge & Locate to the sleeve hole
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_LOAD, abs(nMoveOffset));
        }
        else if(0 > nMoveOffset) {
            // Exit cartridge & Locate to the sleeve hole
            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_QUIT, abs(nMoveOffset));
        }
    }

    qDebug() << Q_FUNC_INFO << nMoveOffset << stMotorState.nTrayOffset;
    return bMoveTray;
}

void UtilityAPI::_moveTrayMotorForAction(ST_MOTOR_STATE &stMotorState, QQueue<ST_FLOW_PROC_ACTION> &queFlowProcAction, QList<QString> &lstAction, int &nCmdIdx, int nStepIdx, const ST_MOTION_INFO &stMotionInfo, int nKitType)
{
    qDebug() << Q_FUNC_INFO << stMotionInfo.nSrcHoleNumber << stMotionInfo.nTarHoleNumber << stMotionInfo.nPreMoveType << stMotionInfo.nCurMoveType << stMotionInfo.nPreAreaType << stMotionInfo.nCurAreaType << nKitType;

    // Obtain the hole offset
    bool bPcr = E_AT_REACTION == stMotionInfo.nCurAreaType ? true : false;
    int nGrapBasePos = 0;
    int nIgnoreOffset = 0;
    int nHoleOffset = 0;
    int nTheoryPosition = 0;

    switch (stMotionInfo.nCurMoveType) {
    case E_MT_PIERCING:
        // Piercing
        nGrapBasePos = MotorVars_T1C::nPiercingOffsetX;
        nIgnoreOffset = 0;
        break;
    case E_MT_SLEEVE:
        // Drivepipe
        nGrapBasePos = MotorVars_T1C::nGrapSleeveOffsetX;
        nIgnoreOffset = MotorVars_T1C::nExtHoleSpacing_B_ab;
        break;
    case E_MT_TIPS:
        // Tipspipe
        if(!bPcr) {
            nGrapBasePos = MotorVars_T1C::nGrapTipsOffsetX;
            nIgnoreOffset = 0;
        }
        else {
            nGrapBasePos = MotorVars_T1C::nGrapPcrPipeOffsetX;
            nIgnoreOffset = 0;
        }
        break;
    default:
        return;
    }

    if(!bPcr) {
        nHoleOffset = _getHoleAbsoluteOffset(nKitType, stMotionInfo.nTarHoleNumber, false);
        nTheoryPosition = nGrapBasePos - abs(nHoleOffset - nIgnoreOffset);
    }
    else {
        nHoleOffset = _getHoleAbsoluteOffset(nKitType, stMotionInfo.nTarHoleNumber, true);
        nTheoryPosition = nGrapBasePos + abs(nHoleOffset - nIgnoreOffset);
    }

    bool bHoleNumberValid = false;
    if(E_AT_SPECIMEN == stMotionInfo.nCurAreaType) {
        bHoleNumberValid = 0 < stMotionInfo.nTarHoleNumber && DF_EXT_A_HOLE_AMOUNT >= stMotionInfo.nTarHoleNumber;
    }
    else if(E_AT_EXTRACT == stMotionInfo.nCurAreaType) {
        bHoleNumberValid = 0 < stMotionInfo.nTarHoleNumber && DF_EXT_HOLE_AMOUNT >= stMotionInfo.nTarHoleNumber;
    }
    else if(E_AT_REACTION == stMotionInfo.nCurAreaType) {
        bHoleNumberValid = 0 < stMotionInfo.nTarHoleNumber && DF_PCR_HOLE_AMOUNT >= stMotionInfo.nTarHoleNumber;
    }

    if(stMotorState.nTrayOffset != nTheoryPosition && bHoleNumberValid) {
        int nMoveOffset =  stMotorState.nTrayOffset - nTheoryPosition;

        int nCmdID = (0 < nMoveOffset) ? E_TP_LOAD : E_TP_QUIT;
        _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, nCmdID, abs(nMoveOffset));

//        if(stMotorState.nTrayOffset > nTheoryPosition) {
//            // Load cartridge & Locate to the sleeve hole
//            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_LOAD, abs(nMoveOffset));
//        }
//        else {
//            // Exit cartridge & Locate to the sleeve hole
//            _insertFlowAction(queFlowProcAction, lstAction, stMotorState, nCmdIdx++, nStepIdx, E_TP_QUIT, abs(nMoveOffset));
//        }
    }

    qDebug() << Q_FUNC_INFO << stMotionInfo.nSrcHoleNumber << stMotionInfo.nTarHoleNumber << nTheoryPosition << nGrapBasePos << nHoleOffset << nIgnoreOffset;
}
